/*
 * $Id: arp.c 50 2009-02-10 05:48:05Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/net/arp.h>
#include <xp/bas/string.h>
#include <xp/bas/locale.h>
#include <xp/bas/memory.h>
#include <xp/bas/endian.h>

#if defined(__linux)
#	include <sys/ioctl.h>
#	include <netinet/in.h>
#	include <net/if_arp.h>
#	include <net/if.h>
#endif

#if 0

#if defined(__FreeBSD__)
	#include <net/ethernet.h>
#elif defined(__linux)
	#include <netpacket/packet.h>
	#include <net/ethernet.h>
#elif defined(sun)||defined(__sun)
	#include <sys/ethernet.h>
#else
	#error not supported	
#endif

xp_arp_t* xp_arp_open (xp_arp_t* arp)
{
	if (arp == XP_NULL) {
		arp = (xp_arp_t*) xp_malloc (xp_XP_SIZEOF(xp_arp_t));
		if (arp == XP_NULL) return XP_NULL;
		arp->__dynamic = xp_true;
	}
	else arp->__dynamic = xp_false;

#if defined(__FreeBSD__)
	/* TODO: is this correct??? */
	if (xp_sck_open (&arp->sck, 
		PF_LINK, SOCK_RAW, xp_hton16(ETHERTYPE_ARP)) == XP_NULL) {
#elif defined(sun)||defined(__sun)
	/* TODO: i don't think this is correct */
	if (xp_sck_open (&arp->sck, 
		PF_INET, SOCK_RAW, xp_hton16(ETHERTYPE_ARP)) == XP_NULL) {
#else
	if (xp_sck_open (&arp->sck, 
		PF_PACKET, SOCK_RAW, xp_hton16(ETH_P_ARP)) == XP_NULL) {
#endif
		if (arp->__dynamic) xp_free (arp);
		return XP_NULL;	
	}

	return arp;
}

int xp_arp_close (xp_arp_t* arp)
{
	if (xp_sck_close(&arp->sck) == -1) return -1;
	if (arp->__dynamic) xp_free (arp);
	return 0;
}

int xp_arp_send (xp_arp_t* arp)
{
	sturct arphdr ah;

	//ah->ar_hrd
	ah->ar_hrd = xp_hton16(ARPHRD_ETHER);
	ah->ar_pro = xp_hton16(ETH_P_IP);
	ah->ar_hln = ME->sll_halen;
	ah->ar_pln = 4;
	ah->ar_op = xp_hton16(ARPOP_REPLY): xp_hton16(ARPOP_REQUEST);
}


int xp_arp_ping4 (xp_arp_t* arp, const xp_char_t* ifce, 
	xp_uint32_t* ip, xp_byte_t mac[XP_ETHER_LEN])
{
	return -1;
}
#endif


/* currently it supports ethernet only */
static int edit_arp_cache_cache (
	int s4, int cmd, const xp_char_t *iface, const xp_ip4addr_t* ipaddr,
	int* flags, xp_ethwaddr_t* hwaddr)
{
#if defined(__linux)
	struct arpreq req;
	xp_size_t len;
	int ret;

	xp_memset (&req, 0, XP_SIZEOF(req));

#if defined(XP_CHAR_IS_MCHAR)
	len = xp_strxcpy (req.arp_dev, XP_COUNTOF(req.arp_dev), iface);
	if (iface[len] != XP_T('\0')) return -1;
#else
	len = xp_wcstomcs (iface, req.arp_dev, XP_COUNTOF(req.arp_dev));
	if (len == (xp_size_t)-1 || iface[len] != XP_T('\0')) return -1;
#endif

	req.arp_pa.sa_family = AF_INET;
	((struct sockaddr_in*)&req.arp_pa)->sin_addr.s_addr = ipaddr->value;

	if (cmd != SIOCGARP)
	{
		req.arp_flags = 0;
		if (*flags & XP_ARP_PERM) req.arp_flags |= ATF_PERM;
		if (*flags & XP_ARP_PUBL) req.arp_flags |= ATF_PUBL;
	}

	if (cmd == SIOCSARP)
	{
		if (hwaddr)
		{
			/* if PUBL is set, this value won't take effect.
			 * the hardware address of the interface seems 
			 * to be taken automatically. */
			req.arp_ha.sa_family = ARPHRD_ETHER;
			xp_memcpy (req.arp_ha.sa_data, hwaddr->value, XP_SIZEOF(hwaddr->value));
		}
		else
		{
			/* get the hardware address from the interface */
			struct ifreq ifr;
			xp_memset (&ifr, 0, XP_SIZEOF(ifr));
#if defined(XP_CHAR_IS_MCHAR)
			len = xp_strxcpy (ifr.ifr_name, XP_COUNTOF(ifr.ifr_name), iface);
			if (iface[len] != XP_T('\0')) return -1;
#else
			len = xp_wcstomcs (iface, ifr.ifr_name, XP_COUNTOF(ifr.ifr_name));
			if (len == (xp_size_t)-1 || iface[len] != XP_T('\0')) return -1;
#endif
			if (ioctl (s4, SIOCGIFHWADDR, &ifr) <= -1) return -1;
			xp_memcpy (&req.arp_ha, &ifr.ifr_hwaddr, XP_SIZEOF(req.arp_ha));
		}
	}

	ret = ioctl (s4, cmd, &req);

	if (ret >= 0 && cmd == SIOCGARP)
	{
		/* SIOCGARP doesn't seem to be able to get a PUBL entry.
		 * Am I wrong? */

		if (req.arp_ha.sa_family != ARPHRD_ETHER) return -1; /* not ethernet */
		xp_memcpy (hwaddr->value, req.arp_ha.sa_data, XP_SIZEOF(hwaddr->value));	

		*flags = 0;
		if (req.arp_flags & ATF_PERM) *flags |= XP_ARP_PERM;
		if (req.arp_flags & ATF_PUBL) *flags |= XP_ARP_PUBL;
	}

	return ret;
#else
	/* not supported */
	return -1;
#endif
}

int xp_upsertarp (
	int s4, const xp_char_t* iface, const xp_ip4addr_t* ipaddr, 
	int flags, const xp_ethwaddr_t* hwaddr)
{
	return edit_arp_cache_cache  (s4, SIOCSARP, iface, ipaddr, &flags, (xp_ethwaddr_t*)hwaddr);
}
	
int xp_deletearp (
	int s4, const xp_char_t* iface, const xp_ip4addr_t* ipaddr, int flags)
{
	return edit_arp_cache_cache  (s4, SIOCDARP, iface, ipaddr, &flags, XP_NULL);
}

int xp_searcharp (
	int s4, const xp_char_t* iface, const xp_ip4addr_t* ipaddr, int* flags, xp_ethwaddr_t* hwaddr)
{
	return edit_arp_cache_cache  (s4, SIOCGARP, iface, ipaddr, flags, hwaddr);
}
